package com.tools.web.http;

import com.fasterxml.jackson.core.type.TypeReference;
import com.tools.common.io.CharsetEnum;
import com.tools.common.object.C;
import com.tools.common.object.json.JsonKit;
import com.tools.common.object.Note;
import com.tools.common.object.string.StrKit;
import com.tools.common.object.string.VerifyKit;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
 * HTTP 响应结果包装对象
 * */
@Note("HTTP 响应结果包装对象")
public final class HttpResult {

    @Note("HTTP 状态码")
    private int statusCode;

    @Note("HTTP 状态描述信息")
    private String statusReason;

    @Note("请求服务器所在区域的信息包装对象")
    private final Locale locale;

    @Note("请求的协议版本对象")
    private ProtocolVersion protocolVersion;

    @Note("响应数据的 MIME 类型")
    private String contentMimeType;

    @Note("响应数据的字符集编码枚举")
    private CharsetEnum contentCharset;

    @Note("响应数据的字节长度")
    private long contentLength;

    @Note("响应头集合")
    private final Map<String, String> responseHeaders;

    @Note("响应体数据: 以字节输入流的形式存储")
    private InputStream contentInputStream;

    @Note("响应体数据: 以字符串的形式存储")
    private String contentString;

    @Note("响应体数据，转为 JavaBean 实体存储")
    private Object contentBean;

    @Note("响应体数据，转为 JavaBean 实体存储")
    private Map<String, Object> contentMap;

    @Note("本对象作为字符串打印输出时，其内容的上下边框")
    private static final String PRINT_BORDER = StrKit.addString("=", 100);

    /* **************************************************************************************
     *
     *          构造器
     *
     * **************************************************************************************
     * */

    public HttpResult(CloseableHttpResponse response) {
        if(response == null) throw new NullPointerException("HTTP 响应信息对象为 null，结果集封装失败");
        this.locale = response.getLocale();
        StatusLine statusLine = response.getStatusLine();
        if(statusLine != null) {
            this.statusCode = statusLine.getStatusCode();
            this.statusReason = statusLine.getReasonPhrase();
            this.protocolVersion = statusLine.getProtocolVersion();
        }
        this.responseHeaders = HttpKit.responseHeaderMap(response);
        HttpEntity entity = response.getEntity();
        private_fillInContentTypeAndCharset(entity);
        private_contentChooseFillIn(entity);
        HttpKit.close(response);
    }

    public static HttpResult of(CloseableHttpResponse response) {
        return new HttpResult(response);
    }

    /* **************************************************************************************
     *
     *          Getter
     *
     * **************************************************************************************
     * */

    public int getStatusCode() {
        return statusCode;
    }

    public String getStatusReason() {
        return statusReason;
    }

    public Map<String, String> getResponseHeaders() {
        return responseHeaders;
    }

    public Locale getLocale() {
        return locale;
    }

    public ProtocolVersion getProtocolVersion() {
        return protocolVersion;
    }

    public String getContentMimeType() {
        return contentMimeType;
    }

    public CharsetEnum getContentCharset() {
        return contentCharset;
    }

    public long getContentLength() {
        return contentLength;
    }

    public InputStream getContentInputStream() {
        return contentInputStream;
    }

    public String getContentString() {
        return (contentString == null) ? "" : contentString;
    }

    public Map<String, Object> getContentMap() {
        if(contentString == null || !VerifyKit.isJsonObject(contentString)) return null;
        if(contentMap != null) return contentMap;
        this.contentMap = JsonKit.toEntity(contentString, new TypeReference<HashMap<String, Object>>() {});
        return contentMap;
    }

    public <T>T getContentBean(C<T> beanType) {
        if(contentString == null || !VerifyKit.isJsonObject(contentString)) return null;
        if(contentBean != null) return (T) contentBean;
        this.contentBean = JsonKit.toEntity(contentString, beanType);
        return (T) contentBean;
    }

    public <T>T getContentBean(TypeReference<T> typeReference) {
        if(contentString == null || !VerifyKit.isJsonObject(contentString)) return null;
        if(contentBean != null) return (T) contentBean;
        this.contentBean = JsonKit.toEntity(contentString, typeReference);
        return (T) contentBean;
    }

    public <T>T getContentBean(Class<T> beanType) {
        if(contentString == null || !VerifyKit.isJsonObject(contentString)) return null;
        if(contentBean != null) return (T) contentBean;
        this.contentBean = JsonKit.toEntity(contentString, beanType);
        return (T) contentBean;
    }


    @Override
    public String toString() {
        return PRINT_BORDER +
                "\n\tstatusCode=" + statusCode +
                "\n\tstatusReason='" + statusReason + '\'' +
                "\n\tlocale=" + locale +
                "\n\tprotocolVersion=" + protocolVersion +
                "\n\tcontentMimeType='" + contentMimeType + '\'' +
                "\n\tcontentCharset=" + contentCharset +
                "\n\tcontentLength=" + contentLength +
                "\n\tresponseHeaders=" + responseHeaders +
                "\n\tcontentInputStream=" + contentInputStream +
                "\n\tcontentString='" + contentString + '\'' +
                "\n\tcontentBean=" + contentBean +
                "\n\tcontentMap=" + StrKit.toFormatString(contentMap, "\t\t\t") + '\n' +
                PRINT_BORDER;
    }


    /* **************************************************************************************
     *
     *          私有逻辑
     *
     * **************************************************************************************
     * */

    @Note("从 HttpEntity 响应实体中设置内容长度、内容 MIME 类型、内容的字符集编码")
    private void private_fillInContentTypeAndCharset(HttpEntity entity) {
        if(entity == null) {
            return;
        }
        this.contentLength = entity.getContentLength();
        Header contentType = entity.getContentType();
        if(contentType != null) {
            String[] array = contentType.toString().split("; ");
            if(array.length != 0) {
                this.contentMimeType = array[0].replace("Content-Type: ", "");
            }
            if(array.length >= 2) {
                String charsetKey = array[1];
                String charset = charsetKey.replace("encoding=", "");
                this.contentCharset = CharsetEnum.getEnum(charset);
            }
        }
    }

    @Note("根据 MIME 类型来决定响应体是字节流存储，还是字符串存储")
    private void private_contentChooseFillIn(HttpEntity entity) {
        if("application/octet-stream".equalsIgnoreCase(contentMimeType)) {
            this.contentInputStream = HttpKit.responseBodyInputStream(entity);
            this.contentString = null;
            return;
        }
        this.contentString = HttpKit.responseBodyString(entity);
    }
}
